﻿/*
 * Copyright (c) Martin Kinkelin
 *
 * See the "License.txt" file in the root directory for infos
 * about permitted and prohibited uses of this code.
 */

using System;
using System.IO;
using System.Diagnostics;
using System.Windows.Forms;
using System.Text;
using Jarf.Properties;

namespace Jarf
{
	/// <summary>
	/// Generates child processes for backup and restore operations.
	/// </summary>
	public static class ProcessFactory
	{
		/// <summary>
		/// Returns a process which will perform a backup of the specified task.
		/// The process needs yet to be started.
		/// </summary>
		/// <param name="task"></param>
		/// <returns></returns>
		public static Process PrepareBackup(BackupTask task)
		{
			return CreateProcess(task, false, false);
		}

		/// <summary>
		/// Returns a process which will restore a backup of the specified task.
		/// The process needs yet to be started.
		/// </summary>
		/// <param name="task"></param>
		/// <param name="deleteExtraneousFiles">
		/// Indicates whether extraneous files and folders (i.e. local files and
		/// folders which do not exist in the backup) are to be deleted.
		/// Excluded items are never deleted, regardless of this value.
		/// </param>
		/// <returns></returns>
		public static Process PrepareRestore(BackupTask task, bool deleteExtraneousFiles)
		{
			return CreateProcess(task, true, deleteExtraneousFiles);
		}


		/// <summary>
		/// Returns an appropriate backup or restore process.
		/// </summary>
		/// <param name="task"></param>
		/// <param name="restoring">
		/// Indicates whether the process is a restore or backup process.
		/// </param>
		/// <param name="deleteExtraneousFiles">
		/// Only regarded if restoring.
		/// Indicates whether extraneous files and folders (i.e. local files and
		/// folders which do not exist in the backup) are to be deleted.
		/// Excluded items are never deleted, regardless of this value.
		/// </param>
		/// <returns></returns>
		private static Process CreateProcess(BackupTask task, bool restoring,
			bool deleteExtraneousFiles)
		{
			if (task == null)
				throw new ArgumentNullException("task");

			if (!Directory.Exists(task.Source))
				throw new InvalidOperationException("The source folder does not exist.");
			if (!task.DestinationIsServer && !Directory.Exists(task.Destination))
				throw new InvalidOperationException("The destination folder does not exist.");

			Process process = new Process();

			process.StartInfo.FileName = (Path.IsPathRooted(Settings.Default.RsyncPath) ?
				Settings.Default.RsyncPath :
				Path.Combine(Application.StartupPath, Settings.Default.RsyncPath));

			if (!File.Exists(process.StartInfo.FileName))
				throw new InvalidOperationException("The Rsync executable cannot be found.");

			bool isWindows = (Path.DirectorySeparatorChar == '\\');

			string source = (isWindows ? ConvertToCygwinPath(task.Source) : task.Source);
			string destination = (task.DestinationIsServer ? task.Destination :
				(isWindows ? ConvertToCygwinPath(task.Destination) : task.Destination));

			if (restoring)
			{
				// swap source and destination
				string previousSource = source;
				source = destination;
				destination = previousSource;
			}

			StringBuilder options = new StringBuilder(Settings.Default.RsyncOptions);

			if (restoring)
			{
				if (deleteExtraneousFiles)
					options.Append(" --delete");
			}
			else
			{
				if (task.DeleteExtraneousFiles)
					options.Append(" --delete --delete-excluded");
				if (!task.OverwriteNewerFiles)
					options.Append(" -u");
			}

			foreach (string item in task.Exclusions)
			{
				options.AppendFormat(" --exclude=\"{0}\"", item);
			}

			process.StartInfo.Arguments = string.Format("{0} \"{1}/\" \"{2}\"",
				options, source, destination);

			// set the "CYGWIN" environment variable to "nontsec" to
			// disable modifying the permissions of files and folders
			if (isWindows)
				process.StartInfo.EnvironmentVariables["CYGWIN"] = "nontsec";

			// store the password in an environment variable to skip the password prompt
			if (task.DestinationIsServer)
				process.StartInfo.EnvironmentVariables["RSYNC_PASSWORD"] = task.Password;

			// hide the process from the user
			process.StartInfo.UseShellExecute = false;
			process.StartInfo.CreateNoWindow = true;

			return process;
		}

		/// <summary>
		/// Converts a Windows path to the corresponding Cygwin path.
		/// </summary>
		/// <param name="path"></param>
		/// <returns></returns>
		private static string ConvertToCygwinPath(string path)
		{
			string cygwinPath;

			if (path[1] == '\\')
			{
				// NetBIOS path
				cygwinPath = path.Replace(Path.DirectorySeparatorChar, '/');
			}
			else if (path[1] == ':')
			{
				// normal path
				cygwinPath = "/cygdrive/" + path[0];
				if (path.Length > 2)
					cygwinPath += path.Substring(2).Replace(Path.DirectorySeparatorChar, '/');
			}
			else
				throw new ArgumentException("Invalid path.");

			return cygwinPath;
		}
	}
}
